//---------------------------------------------------------------------
// Authors: jachymko
//
// Description: Variable names tab expansion
//
// Creation Date: Dec 27, 2006
//
//---------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Provider;
using System.Management.Automation.Runspaces;
using System.Text;
using System.Text.RegularExpressions;

using Microsoft.PowerShell.Commands;

namespace Pscx.Commands.TabExpansion.Handlers
{
    class VariableExpressionHandler : ExpressionHandlerBase
    {
        public VariableExpressionHandler(GetTabExpansionCommand cmd)
            : base(cmd)
        {
        }

        public override void TryExpand(string line, string word)
        {
            Match match = _patternBraces.Match(word);

            if (!match.Success)
            {
                match = _patternNoBraces.Match(word);
            }

            if (match.Success)
            {
                string prefix = match.Groups["Prefix"].Value;
                string scope = match.Groups["Scope"].Value;
                string name = match.Groups["Name"].Value;
                string expr = match.Groups["Expression"].Value;
                bool hasExpr = match.Groups["Expression"].Success;
                bool brace = match.Groups["LBrace"].Success;

                if (hasExpr)
                {
                    string variable = GetVariableName(name, prefix, scope, brace);

                    ExpandExpression(variable, expr);
                }
                else
                {
                    if (string.IsNullOrEmpty(scope))
                    {
                        ExpandDriveName(prefix, scope, name, brace);
                    }

                    ExpandVariableName(prefix, scope, name, brace);
                }
            }
        }

        private void ExpandDriveName(string prefix, string scope, string name, bool leftBrace)
        {
            string script = "Get-PSDrive -Name $args[0]";
            string[] args = new string[]
            {
                name + '*'
            };

            foreach (PSObject obj in InvokeArgs(script, args))
            {
                string driveName = obj.Properties["Name"].Value.ToString();

                if (IsContentProvider(driveName))
                {
                    string expansion = prefix;

                    if (leftBrace)
                        expansion += '{';

                    expansion += driveName + ':';

                    WriteExpansions(expansion);
                }
            }
        }

        private void ExpandVariableName(string prefix, string scope, string name, bool leftBrace)
        {
            if (IsScopeName(scope) || string.IsNullOrEmpty(scope))
            {
                string script = "Get-Variable -Name $args[0]";

                if (!string.IsNullOrEmpty(scope))
                {
                    script += " -Scope $args[1]";
                }

                string[] args = new string[]
                {
                    name + '*', 
                    scope.ToLowerInvariant()
                };

                WriteVariableExpansions(InvokeArgs(script, args), prefix, scope, leftBrace);
            }
            else if (IsContentProvider(scope))
            {
                if (name.StartsWith("\\"))
                {
                    name = name.Substring(1);
                }

                string path = string.Empty;

                int lastSlash = name.LastIndexOf('\\') + 1;

                if(lastSlash > 0)
                {
                    path = name.Substring(0, lastSlash);
                    name = name.Substring(lastSlash);
                }

                StringBuilder arg = new StringBuilder();
                arg.Append(scope);
                arg.Append(":\\");
                arg.Append(path);
                arg.Append(name);
                arg.Append('*');

                string[] args = new string[] { arg.ToString() };
                string script = "Get-ChildItem -Path $args[0]";

                WriteContentProviderExpansions(path, InvokeArgs(script, args), prefix, scope, leftBrace);
            }
        }

        private void WriteContentProviderExpansions(string path, Collection<PSObject> childItems, string prefix, string scope, bool leftBrace)
        {
            foreach(PSObject childItem in childItems)
            {
                PSPropertyInfo piName = childItem.Properties["Name"];
                PSPropertyInfo piIsContainer = childItem.Properties["PSIsContainer"];

                if (piName == null)
                {
                    continue;
                }
            
                string itemName = piName.Value as string;
                if (string.IsNullOrEmpty(itemName))
                {
                    continue;
                }

                if (path.IndexOf('\\') > 0)
                {
                    path = '\\' + path;
                }

                bool rightBrace = true;
                string fullName = path + itemName;

                if (piIsContainer != null && (bool)(piIsContainer.Value))
                {
                    fullName += '\\';
                    rightBrace = false;
                }

                string expandedName = GetVariableName(fullName, prefix, scope, leftBrace, rightBrace);
                WriteExpansions(expandedName);
            }
        }

        private void WriteVariableExpansions(Collection<PSObject> variables, string prefix, string scope, bool leftBrace)
        {
            foreach (PSObject variable in variables)
            {
                PSPropertyInfo piName = variable.Properties["Name"];
                if (piName == null)
                {
                    continue;
                }

                string variableName = piName.Value as string;
                if (string.IsNullOrEmpty(variableName))
                {
                    continue;
                }

                string expandedName = GetVariableName(variableName, prefix, scope, leftBrace);

                WriteExpansions(expandedName);
            }

        }

        private string GetVariableName(string name, string prefix, string scope, bool appendBraces)
        {
            return GetVariableName(name, prefix, scope, appendBraces, true);
        }

        private string GetVariableName(string name, string prefix, string scope, bool leftBrace, bool rightBrace)
        {
            leftBrace |= NeedsBraces(name);

            StringBuilder result = new StringBuilder(prefix);

            if (leftBrace)
                result.Append('{');

            if (!string.IsNullOrEmpty(scope))
            {
                result.Append(scope);
                result.Append(':');
            }

            result.Append(name);

            if (leftBrace && rightBrace)
                result.Append('}');

            return result.ToString();
        }

        private bool NeedsBraces(string name)
        {
            return _needsBraces.IsMatch(name);
        }

        private bool IsScopeName(string s)
        {
            return _scopeNames.IsMatch(s);
        }

        private bool IsContentProvider(string drive)
        {
            PSDriveInfo driveInfo = Command.SessionState.Drive.Get(drive);
            if (driveInfo != null)
            {
                Type providerType = driveInfo.Provider.ImplementingType;
                Type interfaceType = typeof(IContentCmdletProvider);

                return interfaceType.IsAssignableFrom(providerType);
            }

            return false;
        }

        private static readonly Regex _scopeNames = new Regex("(?i)^(private|local|script|global)$");

        private static readonly Regex _needsBraces = new Regex("\\W");

        private static readonly Regex _patternBraces = new Regex(@"(?x) 
            (?<Prefix>.*^\$)
            (?<LBrace>{)
            (
                (?<Scope>[^:]+)
                :
            )?
            (?<Name>[^}]+)
            (?<RBrace>})? 
            (\. 
                (?<Expression>.*)
            )*
            $");

        private static readonly Regex _patternNoBraces = new Regex(@"(?x) 
            (?<Prefix>.*^\$)
            ((?<Scope>[^:]+):)?
            (?<Name>[^.]+)
            (\. 
                (?<Expression>.*)
            )*
            $");
    }
}
